﻿using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using Perspective.Wpf3D.Primitives;
using Perspective.Core.Primitives;
using Perspective.Core.Wpf;

namespace Perspective.Wpf3D.Sculptors
{
    /// <summary>
    /// 组成同心多边形的所有点和三角形集合
    /// A class to handle points and triangles of a polygonal model.
    /// Default radius is 1.0.
    /// </summary>
    public class ConcentricPolygonSculptor : Sculptor
    {
        #region Constant
        internal const double DefaultOuterCircleRadius = 1.0;
        internal const double DefaultInnerCircleRadius = 0.5;
        #endregion

        #region Members
        private Point3DCollection _notRoundedPoints = new Point3DCollection();
        private PolygonSculptor _psOuter;
        private PolygonSculptor _psInner;
        #endregion

         #region Constructors
        /// <summary>
        /// Initializes a new instance of ConcentricPolygonSculptor.
        /// </summary>
        public ConcentricPolygonSculptor()
            : base()
        {
        }

         /// <summary>
        /// Initializes a new instance of ConcentricPolygonSculptor.
        /// Vertices are automatically generated
        /// </summary>
        /// <param name="circumferenceSideCount">Side count</param>
        /// <param name="initialAngle">Angle between the axis [origin - first point] and the X-axis, in degrees</param>
        public ConcentricPolygonSculptor(int circumferenceSideCount, double initialAngle)
        {
            _initialAngle = initialAngle;
            _circumferenceSideCount = circumferenceSideCount;
            CreatePoints();
        }

         /// <summary>
        /// Initializes a new instance of ConcentricPolygonSculptor.
        /// Vertices are automatically generated
        /// </summary>
        /// <param name="circumferenceSideCount">Side count</param>
        /// <param name="initialAngle">Angle between the axis [origin - first point] and the X-axis, in degrees</param>
        /// <param name="roundingRate">Angle rounding rate. The value must be comprized between 0.0 and 0.5.</param>
        public ConcentricPolygonSculptor(int circumferenceSideCount, double initialAngle, double roundingRate)
        {
            Initialize(circumferenceSideCount, initialAngle, roundingRate);
        }

        /// <summary>
        /// Initializes a new instance of ConcentricPolygonSculptor.
        /// Vertices are automatically generated
        /// </summary>
        /// <param name="outerCircleRadius">Circumcircle Radius</param>
        /// <param name="innerCircleRadius ">Circumcircle Radius</param>
        /// <param name="circumferenceSideCount">Side count</param>
        /// <param name="initialAngle">Angle between the axis [origin - first point] and the X-axis, in degrees</param>
        /// <param name="roundingRate">Angle rounding rate. The value must be comprized between 0.0 and 0.5.</param>
        public ConcentricPolygonSculptor(double outerCircleRadius, double innerCircleRadius, int circumferenceSideCount, double initialAngle, double roundingRate)
        {
            Initialize(outerCircleRadius, innerCircleRadius,circumferenceSideCount, initialAngle, roundingRate);
        }

        /// <summary>
        /// Initializes an existing instance of ConcentricPolygonSculptor.
        /// Vertices are automatically generated
        /// </summary>
        /// <param name="circumferenceSideCount">Side count</param>
        /// <param name="initialAngle">Angle between the axis [origin - first point] and the X-axis, in degrees</param>
        /// <param name="roundingRate">Angle rounding rate. The value must be comprized between 0.0 and 0.5.</param>
        public void Initialize(int circumferenceSideCount, double initialAngle, double roundingRate)
        {
            Initialize(DefaultOuterCircleRadius,DefaultInnerCircleRadius, circumferenceSideCount, initialAngle, roundingRate);
        }

        /// <summary>
        /// Initializes an existing instance of ConcentricPolygonSculptor.
        /// Vertices are automatically generated
        /// </summary>
        /// <param name="outerCircleRadius">Circumcircle Radius</param>
        /// <param name="innerCircleRadius ">Circumcircle Radius</param>
        /// <param name="circumferenceSideCount">Side count</param>
        /// <param name="initialAngle">Angle between the axis [origin - first point] and the X-axis, in degrees</param>
        /// <param name="roundingRate">Angle rounding rate. The value must be comprized between 0.0 and 0.5.</param>
        public void Initialize(double outerCircleRadius, double innerCircleRadius, int circumferenceSideCount, double initialAngle, double roundingRate)
        {
            _initialAngle = initialAngle;
            _roundingRate = roundingRate;
            _circumferenceSideCount = circumferenceSideCount;
            _outerCircleRadius = outerCircleRadius;
            _innerCircleRadius = innerCircleRadius;
            CreatePoints();
        }
       #endregion

        #region Properties
        private Point3D _center;

        /// <summary>
        /// The polygon's center point.
        /// </summary>
        public Point3D Center
        {
            get { return _center; }
            set { _center = value; }
        }

        private double _initialAngle;

        /// <summary>
        /// Initial angle (first point), in degrees.
        /// </summary>
        public double InitialAngle
        {
            get { return _initialAngle; }
            set { _initialAngle = value; }
        }

        private double _roundingRate;

        /// <summary>
        /// Gets or sets the angle rounding rate.
        /// The value must be comprized between 0.0 and 0.5.
        /// </summary>
        public double RoundingRate
        {
            get { return _roundingRate; }
            set
            {
                if ((value < 0.0) || (value > 0.5))
                {
                    throw new ArgumentOutOfRangeException("value");
                }
                else
                {
                    _roundingRate = value;
                }
            }
        }

        private int _circumferenceSideCount;

        /// <summary>
        /// 圆周的面数
        /// Gets or sets the side count of the circumference.
        /// </summary>
        protected int CircumferenceSideCount
        {
            get { return _circumferenceSideCount; }
            set { _circumferenceSideCount = value; }
        }

        private double _outerCircleRadius = DefaultOuterCircleRadius;

        /// <summary>
        /// 外圆半径
        /// Gets or sets the radius of the outer circle.
        /// </summary>
        protected double OuterCircleRadius
        {
            get { return _outerCircleRadius; }
            set { _outerCircleRadius = value; }
        }

        private double _innerCircleRadius = DefaultInnerCircleRadius;

        /// <summary>
        /// 内圆半径
        /// Gets or sets the radius of the outer circle.
        /// </summary>
        protected double InnerCircleRadius
        {
            get { return _innerCircleRadius; }
            set { _innerCircleRadius = value; }
        }

        #region OuterPolygonSculptor 外多边形

        /// <summary>
        /// Gets the 1st PolygonSculptor.
        /// </summary>
        public PolygonSculptor OuterPolygonSculptor
        {
            get { return _psOuter; }
        }

        #endregion

        #region InnerPolygonSculptor 内多边形

        /// <summary>
        /// Gets the 2nd PolygonSculptor.
        /// </summary>
        public PolygonSculptor InnerPolygonSculptor
        {
            get { return _psInner; }
        }

        #endregion
        #endregion

         #region Functions
        /// <summary>
        /// Initializes the Points and Triangles collections.
        /// Called By Sculptor.BuildMesh()
        /// </summary>
        protected override void CreateTriangles()
        {
            if (Points.Count == 0)
            {
                CreatePoints();
            }
            if ((Triangles.Count == 0) &&
                (Points.Count > 0))
            {
                BuildTriangles();
            }
        }



        /// <summary>
        /// 计算圆(半径为1)等分点的点位集合
        /// Create the points of the polygon.
        /// </summary>
        protected virtual void CreatePoints()
        {
            if (_circumferenceSideCount < 3)
            {
                throw new ArgumentException("_circumferenceSideCount < 3");
            }
            this.Points.Clear();
            _psOuter = new PolygonSculptor(_outerCircleRadius, _circumferenceSideCount, _initialAngle,_roundingRate);
            _psOuter.RoundingRate = _roundingRate;
            _psOuter.RoundCorner();
            foreach (Point3D outerp in _psOuter.Points)
            {
                this.Points.Add(outerp);
            }

            _psInner = new PolygonSculptor(_innerCircleRadius, _circumferenceSideCount, _initialAngle);
            _psInner.RoundingRate = _roundingRate;
            _psInner.RoundCorner();
            foreach (Point3D innerp in _psInner.Points)
            {
                this.Points.Add(innerp);
            }
       
        }

        /// <summary>
        /// Build the triangles.
        /// </summary>
        public void BuildTriangles()
        {
            BuildTriangles(TriangleSideKind.Front);
        }

        /// <summary>
        /// Build the triangles, with a specific side orientation.
        /// </summary>
        /// <param name="tsk">TriangleSideKind value (triangle orientation).</param>
        public virtual void BuildTriangles(TriangleSideKind tsk)
        {
            // _circumference = 0.0; // forces a refresh of the Circumference property
            this.Triangles.Clear();
            if (Points.Count < 3)
            {
                throw new ArgumentException("Points.Count < 3");
            }
            else
            {
                int outerSideCount = this.Points.Count / 2;
                // center based triangles
                for (int i = 0; i <= outerSideCount - 1; i++)
                {
                    int innerIndex = outerSideCount + i;
                    if ((tsk & TriangleSideKind.Front) == TriangleSideKind.Front)
                    {
                        if (i < outerSideCount - 1)
                        {
                            this.Triangles.Add(new Point3DTriplet(Points[i], Points[innerIndex + 1], Points[innerIndex]));
                            this.Triangles.Add(new Point3DTriplet(Points[i], Points[i + 1], Points[innerIndex + 1]));
                        }
                        else
                        {
                            this.Triangles.Add(new Point3DTriplet(Points[i], Points[outerSideCount], Points[innerIndex]));
                            this.Triangles.Add(new Point3DTriplet(Points[i], Points[0], Points[outerSideCount]));
                        }
                    }
                    if ((tsk & TriangleSideKind.Back) == TriangleSideKind.Back)
                    {
                        if (i < outerSideCount - 1)
                        {
                            this.Triangles.Add(new Point3DTriplet(Points[i], Points[innerIndex], Points[innerIndex + 1]));
                            this.Triangles.Add(new Point3DTriplet(Points[i], Points[innerIndex + 1], Points[i + 1]));
                        }
                        else
                        {
                            this.Triangles.Add(new Point3DTriplet(Points[i], Points[innerIndex], Points[outerSideCount]));
                            this.Triangles.Add(new Point3DTriplet(Points[i], Points[outerSideCount], Points[0]));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// A method for building the TextureCoordinates collection of the mesh.
        /// </summary>
        internal override void MapTexture()
        {
            foreach (Point3DTriplet pt in this.Triangles)
            {
                foreach (Point3D p in pt.Points)
                {
                    Mesh.TextureCoordinates.Add(
                        new Point(p.X, -p.Y));
                }
            }
        }
        #endregion
    }
}
